package org.icop.frame.eval;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.icop.model.ValuedCorrespondence;
import org.icop.utils.TransitiveClosureCache;
import org.jbpt.algo.graph.TransitiveClosure;
import org.jbpt.graph.abs.IDirectedEdge;
import org.jbpt.graph.abs.IDirectedGraph;
import org.jbpt.hypergraph.abs.IVertex;

/**
 * This evaluator scores a mapping based on whether the 
 * partial order between nodes is preserved. We iterate over 
 * the Cartesian product of matches. For each pair of matches,
 * we check whether all order relations for the nodes of both 
 * sets in one graph are equal the order relations for all nodes
 * of both sets in the other graph.
 * 
 * The ratio of preserved relations and all relations is the score
 * for a pair of matches.
 * 
 * The average score over all pairs of matches is the score
 * for the mapping.
 * 
 * @author matthias.weidlich
 *
 */
public class PartialOrderEvaluator<G extends IDirectedGraph<E,V>, E extends IDirectedEdge<V>, V extends IVertex> implements Evaluator<G, E, V> {

	protected TransitiveClosure<E, V> closure1;
	protected TransitiveClosure<E, V> closure2;
	
	@SuppressWarnings("unchecked")
	@Override
	public double evaluate(G sg1, G sg2, Set<ValuedCorrespondence<V>> mapping) {
				
		/*
		 * Partial order cannot be compared for one match.
		 */
		if (mapping.size() < 2)
			return 1;
		
		closure1 = (TransitiveClosure<E, V>) TransitiveClosureCache.getInstance().getTransitiveClosureForGraph((IDirectedGraph<IDirectedEdge<IVertex>, IVertex>) sg1);
		closure2 = (TransitiveClosure<E, V>) TransitiveClosureCache.getInstance().getTransitiveClosureForGraph((IDirectedGraph<IDirectedEdge<IVertex>, IVertex>) sg2);

		double score = 0;

		/*
		 * As the score metric is symmetric, we can use a cache.
		 */
		Map<ValuedCorrespondence<V>, Map<ValuedCorrespondence<V>,Double>> scoreCache = new HashMap<ValuedCorrespondence<V>, Map<ValuedCorrespondence<V>,Double>>();
		
		for(ValuedCorrespondence<V> match1 : mapping) {
			for(ValuedCorrespondence<V> match2 : mapping) {
				if (match1.equals(match2))
					continue;
				score += getScore(scoreCache, match1, match2); 
			}
		}
		
		/*
		 * Return average of scores
		 */
		return score / mapping.size();
	}

	protected double getScore(Map<ValuedCorrespondence<V>, Map<ValuedCorrespondence<V>,Double>> scoreCache, ValuedCorrespondence<V> match1, ValuedCorrespondence<V> match2) {
		if (scoreCache.containsKey(match1))
			if (scoreCache.get(match1).containsKey(match2))
				return scoreCache.get(match1).get(match2);
		if (scoreCache.containsKey(match2))
			if (scoreCache.get(match2).containsKey(match1))
					return scoreCache.get(match2).get(match1);

		double score = calculateScore(match1, match2);

		writeToCache(scoreCache, match1, match2, score);
		writeToCache(scoreCache, match2, match1, score);

		return score;
	}
	
	protected void writeToCache(Map<ValuedCorrespondence<V>, Map<ValuedCorrespondence<V>,Double>> scoreCache, ValuedCorrespondence<V> match1, ValuedCorrespondence<V> match2, double score) {
		if (scoreCache.containsKey(match1)) {
			scoreCache.get(match1).put(match2, score);
		}
		else {
			Map<ValuedCorrespondence<V>,Double> map = new HashMap<ValuedCorrespondence<V>, Double>();
			map.put(match2, score);
			scoreCache.put(match1, map);
		}
	}
	
	protected double calculateScore(ValuedCorrespondence<V> match1, ValuedCorrespondence<V> match2) {
		
		double countRelations = match1.getV1s().size() * match2.getV1s().size() * match1.getV2s().size() * match2.getV2s().size();
		double preservedRelations = 0;
		
		for (V m1s1 : match1.getV1s()) {
			for (V m2s1 : match2.getV1s()) {
				for (V m1s2 : match1.getV2s()) {
					for (V m2s2 : match2.getV2s()) {
						if (closure1.hasPath(m1s1, m2s1) == closure2.hasPath(m1s2, m2s2))
							preservedRelations++;
					}
				}
			}
		}
		return preservedRelations / countRelations;
	}

}
